/* eslint-disable class-methods-use-this */
/* eslint-disable prefer-template */

import { DbColumn } from '../../models/dbColumn'
import { DbForeignKey } from '../../models/dbForeignKey'
import { DbIndex } from '../../models/dbIndex'
import { DbModel } from '../../models/dbModel'
import { DbTable } from '../../models/dbTable'
import { DbProvider, line } from '../core/dbProvider'

export class SqliteProvider extends DbProvider {
	private SQL_Statement_End = ';'

	private IdentifierStart = '"'

	private IdentifierEnd = '"'

	private OutputID_FirstSection = 1

	private OutputID_SecondSection = 2

	public getCreateTableIfNotExistsSql(model: DbModel): string {
		this.outputInitialize()

		model.tables.forEach((table: DbTable) => {
			const { sqlCreateTable } = this.getSqlCreateTableIfNotExistsSingle(table)
			this.outputlnln(sqlCreateTable)
		})

		return this.outputCopy()
	}

	private getCreateTableInternal(model: DbModel, withComment: boolean) {
		model.tables.forEach((table: DbTable) => {
			const { sqlCreateTable, sqlCreateFK, sqlComment } = this.getSqlCreateTableSingle(
				table,
				withComment,
				false,
			)
			this.outputlnln(sqlCreateTable)
			if (sqlCreateFK.length > 0) {
				this.outputlnln(sqlCreateFK + line + this.SQL_Statement_End, 1)
			}
			if (withComment) {
				if (sqlComment.length > 0) {
					this.outputlnln(sqlComment + line + this.SQL_Statement_End, 2)
				}
			}
		})
	}

	public getCreateTableSql(model: DbModel, withComment: boolean): string {
		this.outputInitialize()
		this.getCreateTableInternal(model, withComment)
		return this.outputCopy()
	}

	private getDropTableInternal(model: DbModel) {
		model.tables.forEach((table: DbTable) => {
			const sSQL = this.getSqlDropTableSingle(table)
			this.outputlnln(sSQL)
		})
	}

	public getDropTableSql(model: DbModel): string {
		this.outputInitialize()
		this.getDropTableInternal(model)
		return this.outputCopy()
	}

	public getDropThenCreateTableSql(model: DbModel, withComment: boolean): string {
		this.outputInitialize()
		this.getDropTableInternal(model)
		this.getCreateTableInternal(model, withComment)
		return this.outputCopy()
	}

	private getSqlCreateTableIfNotExistsSingle(table: DbTable): {
		sqlComment: string
		sqlCreateFK: string
		sqlCreateTable: string
	} {
		return this.getSqlCreateTableSingle(table, false, true)
	}

	public getSqlCreateTableSingle(
		table: DbTable,
		withComment: boolean,
		IfNotExists: boolean,
	): {
		sqlComment: string
		sqlCreateFK: string
		sqlCreateTable: string
	} {
		const result = {
			sqlComment: '',
			sqlCreateFK: '',
			sqlCreateTable: '',
		}

		if (!table.columns) {
			return result
		}
		// Create Columns sql
		let sqlColumn = ''
		table.columns.forEach((column: DbColumn, index) => {
			const sqlComma = index === 0 ? ' ' : ','
			const sqlNullable = column.nullable ? 'NULL' : 'NOT NULL'
			const sqlDefault =
				column.default!.length === 0
					? ''
					: ` CONSTRAINT ${this.renderDefaultName(table, column)} DEFAULT (${column.default})`
			sqlColumn += line + `  ${sqlComma}"${column.name}" ${column.type} ${sqlNullable}${sqlDefault}`
		})

		// Primary key SQL
		let sqlPrimaryKey = ''
		if (table.primaryKey) {
			const sqlPkName = this.renderPkName(table)
			const sqlPkColumns = table.primaryKey.columns.replace(',\\s*', ', ')
			sqlPrimaryKey = line + `  ,CONSTRAINT ${sqlPkName} PRIMARY KEY (${sqlPkColumns})`
		}

		// unique Indexes SQL
		let sqlUniqueIndex = ''
		table.indexes?.forEach((index: DbIndex) => {
			if (index.unique) {
				const sqlIndexName = this.renderIndexName(table, index)
				const sqlColumns = index.columns.replace(',\\s*', ', ')
				sqlUniqueIndex += line + `  ,CONSTRAINT ${sqlIndexName} UNIQUE (${sqlColumns})`
			}
		})

		// non-unique Indexes SQL
		let sqlNoUniqueIndex = ''
		table.indexes?.forEach((index: DbIndex) => {
			if (!index.unique) {
				const sqlIndexIfNotExist = IfNotExists ? 'IF NOT EXISTS ' : ''
				const sqlIndexName = this.renderIndexName(table, index)
				const sqlColumns = index.columns.replace(',\\s*', ', ')
				sqlNoUniqueIndex +=
					line +
					`CREATE INDEX ${sqlIndexIfNotExist}${sqlIndexName} ON "${table.name}" (${sqlColumns})` +
					this.SQL_Statement_End
			}
		})

		// Generate Foreign key SQL
		// SQLite does not support Add FK in Alter Table statement.
		let sqlForeignKey = ''
		if (table.foreignKeys.length > 0) {
			table.foreignKeys?.forEach((foreignKey: DbForeignKey) => {
				const sqlFkName = this.renderFkName(table, foreignKey)
				const sqlColumns = foreignKey.columns.replace(',\\s*', ', ')
				const sqlRefInfo = `${foreignKey.refTable}(${foreignKey.refColumns.replace(',\\s*', ', ')})`
				const sqlOptions = foreignKey.options!.length > 0 ? foreignKey.options : ''

				sqlForeignKey +=
					line +
					`  ,CONSTRAINT ${sqlFkName} FOREIGN KEY (${sqlColumns}) REFERENCES ${sqlRefInfo}${sqlOptions}`
			})
		}

		// Generate table sql
		const sqlIfNotExist = IfNotExists ? 'IF NOT EXISTS ' : ''
		result.sqlCreateTable =
			`CREATE TABLE ${sqlIfNotExist}"${table.name}" (` +
			sqlColumn +
			sqlPrimaryKey +
			sqlUniqueIndex +
			sqlForeignKey +
			line +
			')' +
			this.SQL_Statement_End +
			sqlNoUniqueIndex

		return result
	}

	private getSqlDropTableSingle(table: DbTable): string {
		const sSQL =
			'DROP TABLE IF EXISTS ' +
			this.IdentifierStart +
			table.name +
			this.IdentifierEnd +
			this.SQL_Statement_End

		return sSQL
	}
}
